/**
 * \file    tester.h
 * \brief   Parser generator.
 * \author  Jérôme LAURENS
 * \version 0.1
 * \date    November 5, 2014
 *
 * Reads a LALR(1) grammar, generates a parser based on a template file.
 * This is based on lemon parser generator, but has strongly been
 * revisited.
 *
 */

#ifndef TESTER_H
#define TESTER_H

#include "common.h"
#include "state.h"
#include "symbol.h"
#include "item.h"
#include "builder.h"
#include "driver.h"
#include "generator.h"
#include "parser.h"
#include "grammar.h"
#include "description.h"
#include <sstream>
#include <stack>
#include <boost/range/adaptor/reversed.hpp>

LPP_NAMESPACE_BEGIN

namespace LEMONPP_UNIT_TEST {

struct Tested {
    Tested() {
        description_m = "Simple grammar, 1 terminal(s), 2 non terminal(s), 1 rule(s)";
        input_m = "";
        number_of_rules_m = 0;
        number_of_states_m = 0;
        number_of_parser_errors_m = 0;
        number_of_generator_errors_m = 0;
        expected_prepared_actions_m = "";
        expected_lookaheads_m = "";
        expected_shift_offsets_m = "";
        expected_reduce_offsets_m = "";
        expected_defaults_m = "";
        expected_symbol_names_m = "";
        expected_rule_names_m = "";
        expected_actions_m = {};
        expected_nullable_m = {};
        state_map_m = {};
    }
    String description_m;
    String input_m;
    Index number_of_rules_m;
    Index number_of_states_m;
    Index number_of_parser_errors_m;
    Index number_of_generator_errors_m;
    StringLists yes_terminal_lists_m;
    StringLists  no_terminal_lists_m;

    StringSet expected_nullable_m;
    StringSet expected_actions_m;
    String expected_prepared_actions_m;
    String expected_lookaheads_m;
    String expected_shift_offsets_m;
    String expected_reduce_offsets_m;
    String expected_defaults_m;
    String expected_symbol_names_m;
    String expected_rule_names_m;
    String expected_rule_informations_m;
    typedef std::map<Index,Index> StateMap;
    StateMap state_map_m;
};
struct Tester {
    void test_nullable (bool display_found) {
        std::cout<<"Testing nullable... ";
        StringSet found;
        for (SymbolRef const & symbol: grammar_m->symbols()) {
            std::ostringstream oss;
            if (symbol->is_nullable()) {
                found.insert(symbol->name());
            }
        }
        if (display_found) {
            std::cout<<"FOUND:    ";
            for (String const & s: found) {
                std::cout<<std::endl<<", \""<<s<<"\"";
            }
            std::cout<<"EXPECTED: ";
            for (String const & s: tested_m.expected_nullable_m) {
                std::cout<<std::endl<<", \""<<s<<"\"";
            }
        }
        BOOST_REQUIRE(tested_m.expected_nullable_m==found);
        std::cout<<"OK"<<std::endl;
    }
    void test_actions(bool display_found) {
        StringSet expected = tested_m.expected_actions_m;
        if (expected.size()>0) {
            std::cout<<"Testing actions... ";
            StringSet found;
            for (StateRef const & state: generator_m.states()) {
                for (ActionRef const & action: state->actions()) {
                    std::ostringstream oss;
                    oss<<state->index()<<": ";
                    oss<<Description()(action);
                    found.insert(oss.str());
                }
            }
            if (display_found) {
                std::cout<<"FOUND:"<<std::endl;
                for(String const & s: found) {
                    std::cout<<s<<std::endl;
                }
                std::cout<<"EXPECTED:"<<std::endl;
                for(String const & s: expected) {
                    std::cout<<s<<std::endl;
                }
            }
            BOOST_REQUIRE(tested_m.expected_actions_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    void test_prepared_actions (bool display_found) {
        String expected = tested_m.expected_prepared_actions_m;
        if (expected.size()>0) {
            std::cout<<"Testing prepared actions... ";
            String found = driver_m.test_prepared_actions();
            if (display_found) {
                std::cout<<"FOUND:    "<<found<<std::endl;
                std::cout<<"EXPECTED: "<<expected<<std::endl;
            }
            BOOST_REQUIRE(tested_m.expected_prepared_actions_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    void test_lookaheads(bool display_found) {
        String expected = tested_m.expected_lookaheads_m;
        if (expected.size()>0) {
            std::cout<<"Testing lookaheads... ";
            String found = driver_m.test_lookaheads();
            if (display_found){
                std::cout<<"FOUND:    "<<found<<std::endl;
                std::cout<<"EXPECTED: "<<expected<<std::endl;
            }
            BOOST_REQUIRE(tested_m.expected_lookaheads_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    void test_shift_offsets(bool display_found) {
        String expected = tested_m.expected_shift_offsets_m;
        if (expected.size()>0) {
            std::cout<<"Testing shift offset... ";
            String found = driver_m.test_shift_offsets();
            if (display_found) {
                std::cout<<"FOUND:    "<<found<<std::endl;
                std::cout<<"EXPECTED: "<<expected<<std::endl;
            }
            BOOST_REQUIRE(tested_m.expected_shift_offsets_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    void test_reduce_offsets(bool display_found) {
        String expected = tested_m.expected_reduce_offsets_m;
        if (expected.size()>0) {
            std::cout<<"Testing reduce offset... ";
            String found = driver_m.test_reduce_offsets();
            if (display_found) {
                std::cout<<"FOUND:    "<<found<<std::endl;
                std::cout<<"EXPECTED: "<<expected<<std::endl;
            }
            BOOST_REQUIRE(tested_m.expected_reduce_offsets_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    void test_symbol_names(bool display_found) {
        String expected = tested_m.expected_symbol_names_m;
        if (expected.size()>0) {
            std::cout<<"Testing tokens... ";
            String found = driver_m.test_symbol_names();
            if (display_found) {
                std::cout<<"FOUND:    "<<found<<std::endl;
                std::cout<<"EXPECTED: "<<expected<<std::endl;
                std::cout<<"            expected_symbol_names_m = \""<<found<<"\";"<<std::endl;
            }
            BOOST_REQUIRE(tested_m.expected_symbol_names_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    void test_rule_names(bool display_found) {
        String expected = tested_m.expected_rule_names_m;
        if (expected.size()>0) {
            std::cout<<"Testing rules... ";
            String found = driver_m.test_rule_names();
            if (display_found) {
                std::cout<<"FOUND:    "<<found<<std::endl;
                std::cout<<"EXPECTED: "<<expected<<std::endl;
                std::cout<<"            expected_rule_names_m = \""<<found<<"\";"<<std::endl;
            }
            BOOST_REQUIRE(tested_m.expected_rule_names_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    void test_rule_informations(bool display_found) {
        String expected = tested_m.expected_rule_informations_m;
        if (expected.size()>0) {
            std::cout<<"Testing rule informations... ";
            String found = driver_m.test_rule_informations();
            if (display_found) {
                std::cout<<"FOUND:    "<<found<<std::endl;
                std::cout<<"EXPECTED: "<<expected<<std::endl;
            }
            BOOST_REQUIRE(tested_m.expected_rule_informations_m==found);
            std::cout<<"OK"<<std::endl;
        }
    }
    bool test_list(StringList const & list) {
#ifndef NDEBUG
        std::ostringstream oss;
        String separator = "";
        for (String const & S: list) {
            oss<<separator<<S;
            separator = " ";
        }
        DEBUG_MESSAGE("PARSING: %s",oss.str().c_str());
#endif
        std::stack<SymbolRef> input;
        for (String const & S: boost::adaptors::reverse(list)) {
            if (!S.empty()) {
                DEBUG_MESSAGE("Recording symbol: %s",S.c_str());
                std::smatch result;
                const Regex key_rx(Parser::here_str+Parser::key_str);
                if (std::regex_search(S.begin(), S.end(), result, key_rx)) {
                    BOOST_REQUIRE(grammar_m->has_symbol_for_shortcut(S));
                    SymbolRef const & terminal = grammar_m->symbol_for_shortcut(S);
                    BOOST_REQUIRE(terminal->is_terminal());
                    input.push(terminal);
                } else {
                    BOOST_REQUIRE(grammar_m->has_symbol_for_name(S));
                    SymbolRef const & terminal = grammar_m->symbol_for_name(S);
                    BOOST_REQUIRE(terminal->is_terminal());
                    input.push(terminal);
                }
            }
        }
        std::stack<String> descriptions;
        std::stack<StateRef> states;
        StateRef current_state = generator_m.state_at_index(0);
        states.push(current_state);
        while (true) {
            SymbolRef current_symbol = input.empty()? grammar_m->eof_symbol():input.top();
            std::cout<<(Format("State #%1%, Lookahead %2%")%current_state->index()%current_symbol->short_name()).str();
            for (ItemRef const & i: current_state->closure_items()) {
                if (!i->is_at_end() && current_symbol==i->symbol_after_dot()) {
                    std::cout<<(Format(", %s")%Description()(i)).str();
                }
            }
            std::cout<<std::endl;
            ActionRef action = builder_m.coder().action(current_state,current_symbol);
            if (action->is_error()) {
                std::cout << "PARSING FAILURE: ERROR action"<<std::endl;
                while (!descriptions.empty()) {
                    std::cout<<descriptions.top();
                    descriptions.pop();
                }
                return false;
            } else if (action->is_shift()) {
                states.push(current_state = action->shift_state());
                std::cout<<(Format("SHIFT %1%/%2%\n")%current_state->index()%Description()(current_state)).str();
                if (!input.empty()) input.pop();
                descriptions.push(current_symbol->short_name());
            } else if (action->is_reduce()) {
                RuleRef const reduce_rule = action->reduce_rule();
                Index n = reduce_rule->rhs_symbols().size();
                std::ostringstream oss;
                String close = "";
                if (n>1) {
                    oss<<"(";
                    close = ")";
                }
                String prefix = "";
                while (n-->0) {
                    if (states.empty()) {
                        std::cout<<(Format("MISSING reduce state\n")).str();
                        while (!descriptions.empty()) {
                            std::cout<<descriptions.top();
                            descriptions.pop();
                        }
                        return false;
                    }
                    states.pop();
                    oss<<prefix;
                    prefix = ",";
                    if (descriptions.empty()) {
                        oss<<"$";
                    } else {
                        oss<<descriptions.top();
                        descriptions.pop();
                    }
                }
                oss<<close;
                if (states.empty()) {
                    std::cout << "PARSING FAILURE: MISSING shift state after reduce"<<std::endl;
                    while (!descriptions.empty()) {
                        std::cout<<descriptions.top();
                        descriptions.pop();
                    }
                    return false;
                }
                current_state = states.top();
                current_symbol = reduce_rule->lhs_symbol();
                if (current_symbol->is_accept()) {
                    std::cout<<"PARSING SUCCESS" << std::endl;
                    std::cout<<oss.str()<<std::endl;
                    return true;
                }
                action = builder_m.coder().action(current_state,current_symbol);
                BOOST_REQUIRE(action->is_shift());
                std::cout<<(Format("REDUCE %1%/%2%\n")%reduce_rule->index()%Description()(reduce_rule)).str();
                states.push(current_state = action->shift_state());
                descriptions.push(oss.str());
            } else {
                std::cout<<(Format("PARSING FAILURE: ERROR action\n")).str();
                while (!descriptions.empty()) {
                    std::cout<<descriptions.top();
                    descriptions.pop();
                }
                return false;
            }
            if (states.empty()) {
                if (input.empty()) {
                    std::cout << "PARSING FAILURE: Too many terminals" << std::endl;
                } else {
                    std::cout << "PARSING FAILURE: Too many terminals" << std::endl;
                }
                while (!descriptions.empty()) {
                    std::cout<<descriptions.top();
                    descriptions.pop();
                }
                return false;
            }
        }
    }
    void test_terminal_lists(bool /*display_found*/) {
        std::cout<<"Testing terminal lists... ";
        Index n = 0;
        for (StringList const & Ss: tested_m.yes_terminal_lists_m) {
            if (!test_list(Ss)) {
                ++n;
            }
        }
        for (StringList const & Ss: tested_m.no_terminal_lists_m) {
            if (test_list(Ss)) {
                ++n;
            }
        }
        BOOST_REQUIRE(n==0);
        std::cout<<"OK"<<std::endl;
    }
    Tester(Tested const & tested, StringSet & set, bool display_found = false):
        tested_m(tested)
      , grammar_m(Grammar::Make())
      , parser_m("?", grammar_m, tested.input_m, set)
      , generator_m("?", grammar_m)
      , builder_m(generator_m)
      , driver_m(builder_m) {
        DEBUG_MESSAGE("TABLES: ************************************************************");
        std::cout<<tested_m.description_m<<std::endl;
        std::cout<<tested_m.input_m<<std::endl;
        BOOST_REQUIRE(parser_m.number_of_errors()==tested_m.number_of_parser_errors_m);
        BOOST_REQUIRE(generator_m.number_of_errors()==tested_m.number_of_generator_errors_m);
        BOOST_REQUIRE(grammar_m->number_of_rules()==tested_m.number_of_rules_m+1);
#ifndef NDEBUG
        DEBUG_MESSAGE("ACTIONS:");
        for (StateRef const & state: generator_m.states()) {
            for (ActionRef const & action: state->actions()) {
                Format F("(%1%/%2%,%3%)=>%4%");
                F%state->index()%state->id();
                F%action->lookahead()->short_name();
                F%Description()(action);
                DEBUG_MESSAGE("%s",F.str().c_str());
            }
        }
#endif
        StringSet expected_actions;
        for (StateRef const & state: generator_m.states()) {
            for (SymbolRef const & lookahead: grammar_m->lookahead_symbols()) {
                Format F("(%1%/%2%,%3%)=>%4%");
                F%state->index()%state->id();
                F%lookahead->name();
                ActionRef const & action = state->action(lookahead);
                if (action) {
                    F%Description()(action);
                } else {
                    F%Description()(Action::Make(Action::Type::ERROR,lookahead));
                }
                expected_actions.insert(F.str());
            }
        }
        StringSet const simple_built_actions = builder_m.simple_built_actions();
        if (simple_built_actions!=expected_actions) {
            DEBUG_MESSAGE("*************** EXPECTED STATE ACTIONS:");
            for (String const & s: expected_actions) {
                if (std::find(simple_built_actions.begin(),simple_built_actions.end(),s)==simple_built_actions.end()) {
                    DEBUG_MESSAGE("MISSING EXPECTED     %s",s.c_str());
                }
            }
            DEBUG_MESSAGE("*************** SIMPLE BUILT STATE ACTIONS:");
            for (String const & s: simple_built_actions) {
                if (std::find(expected_actions.begin(),expected_actions.end(),s)==expected_actions.end()) {
                    DEBUG_MESSAGE("MISSING SIMPLE BUILT %s",s.c_str());
                }
            }
            throw std::logic_error("Built state actions consistency test failed, simple case.");
        }
        StringSet const built_actions = builder_m.built_actions();
        if (built_actions!=expected_actions) {
            DEBUG_MESSAGE("*************** EXPECTED STATE ACTIONS:");
            for (String const & s: expected_actions) {
                if (std::find(built_actions.begin(),built_actions.end(),s)==built_actions.end()) {
                    DEBUG_MESSAGE("MISSING EXPECTED %s",s.c_str());
                }
            }
            DEBUG_MESSAGE("*************** BUILT STATE ACTIONS:");
            for (String const & s: built_actions) {
                if (std::find(expected_actions.begin(),expected_actions.end(),s)==expected_actions.end()) {
                    DEBUG_MESSAGE("MISSING BUILT    %s",s.c_str());
                }
            }
            DEBUG_MESSAGE("*************** ALL EXPECTED STATE ACTIONS:");
            for (String const & s: expected_actions) {
                DEBUG_MESSAGE("EXPECTED %s",s.c_str());
            }
            DEBUG_MESSAGE("*************** ALL BUILT STATE ACTIONS:");
            for (String const & s: built_actions) {
                DEBUG_MESSAGE("BUILT    %s",s.c_str());
            }
            DEBUG_MESSAGE("*************** ACTIONS TABLE:");
            for (SymbolRef const & symbol: grammar_m->terminals()) {
                DEBUG_MESSAGE("symbol %lu->%s",symbol->index(),symbol->name().c_str());
            }
            for (StateRef const & state: generator_m.states()) {
                DEBUG_MESSAGE("offset %lu->%ld",state->index(),state->terminal_offset());
            }
            for (Transactions::value_type const & value: builder_m.action_table()) {
                DEBUG_MESSAGE("%lu->(%lu,%lu)",value.first,value.second.lookahead(),value.second.action());
            }
            for (SymbolRef const & symbol: grammar_m->non_terminals()) {
                DEBUG_MESSAGE("symbol %lu->%s",symbol->index(),symbol->name().c_str());
            }
            for (StateRef const & state: generator_m.states()) {
                DEBUG_MESSAGE("offset %lu->%ld",state->index(),state->non_terminal_offset());
            }
            for (Transactions::value_type const & value: builder_m.action_table()) {
                DEBUG_MESSAGE("%lu->(%lu,%lu)",value.first,value.second.lookahead(),value.second.action());
            }
            DEBUG_MESSAGE("**********  ALL WEIRD ACTIONS FROM CODER(STATE,SYMBOL):");
            for (StateRef const & S: generator_m.states()) {
                for (SymbolRef const & SS: grammar_m->lookahead_symbols()) {
                    ActionRef A = builder_m.coder().action(S,SS);
                    ActionRef B = S->action(SS);
                    if (A!=B && (!A->is_error()||B)) {
                        DEBUG_MESSAGE("%s: %s built    action: %s",Description()(S).c_str(),SS->name().c_str(),Description()(A).c_str());
                        DEBUG_MESSAGE("%s: %s expected action: %s",Description()(S).c_str(),SS->name().c_str(),Description()(B).c_str());
                    }
                }
            }
            throw std::logic_error("Built state actions consistency test failed.");
        }
        test_terminal_lists(display_found);
        DEBUG_MESSAGE("*************** TEST DONE");
        test_symbol_names(display_found);
        return;
        test_rule_names(display_found);
        test_rule_informations(display_found);
        test_nullable(display_found);
        //test_1st_sets(display_found);
        test_actions(display_found);
        test_prepared_actions(display_found);
        test_lookaheads(display_found);
        test_shift_offsets(display_found);
        test_reduce_offsets(display_found);
        BOOST_REQUIRE(generator_m.states().size()==tested_m.number_of_states_m);
        BOOST_REQUIRE(generator_m.number_of_errors()==0);
    }
protected:
    Tested tested_m;
    GrammarRef grammar_m;
    Parser parser_m;
    Generator generator_m;
    Builder builder_m; /*! action table builder */
    Driver driver_m; /*! output driver */
};

};

LPP_NAMESPACE_END

#endif // TESTER_H
